package cn.sell.bpmn.repository.impl;

import cn.sell.bpmn.repository.BpmReposittoryService;
import lombok.extern.log4j.Log4j2;
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.*;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

/**
 * 1、部署流程图
 * <p>
 * 2、对应的表
 * act_re_deployment：部署对象表
 * act_re_procdef：流程定义表
 * act_ge_bytearray：资源文件表
 * act_ge_property：主键生成策略表
 */
@Log4j2
@Service
//@Transactional
public class BpmReposittoryServiceImpl implements BpmReposittoryService {

    @Autowired
    private RepositoryService repositoryService; //与流程定义和部署相关的service

    /**
     * 通过字符串部署流程
     *
     * @param processName  部署流程的名称
     * @param bpmnContext  流程图文本xml格式
     * @param resourceName 流程图资源名称
     * @return
     */
    @Override
    public Deployment DeploymentProcessDefinition(String processName, String bpmnContext, String resourceName) {
        Deployment deployment = repositoryService
                .createDeployment() // 创建一个部署对象
                .name(processName) // 添加部署流程的名称，
                .addString(resourceName, bpmnContext) // 流程图字符串部署, 文件名称
                .deploy();// 完成部署
        log.info("部署ID：" + deployment.getId());
        log.info("部署名称：" + deployment.getName());
        return deployment;
    }

    /**
     * 通过文件流部署流程
     *
     * @param processName     部署流程的名称
     * @param zipInputStream 流程图文件流,zip文件流
     * @return
     */
    @Override
    public Deployment DeploymentProcessDefinition(String processName, ZipInputStream zipInputStream) {
        Deployment deployment = repositoryService
                .createDeployment() // 创建一个部署对象
                .name(processName) // 添加部署流程的名称，
                .addZipInputStream(zipInputStream) // 从文件流中部署流程图
                .deploy();// 完成部署
        log.info("部署ID：" + deployment.getId());
        log.info("部署名称：" + deployment.getName());
        return deployment;
    }

    @Override
    public Deployment DeploymentProcessDefinition(String processName, String resourceName, BpmnModel bpmnModel) {
        Deployment deployment = repositoryService
                .createDeployment() // 创建一个部署对象
                .name(processName) // 添加部署流程的名称，
                .addBpmnModel(resourceName, bpmnModel)
                .deploy();// 完成部署
        log.info("部署ID：" + deployment.getId());
        log.info("部署名称：" + deployment.getName());
        return deployment;
    }

    /**
     * 查询流程定义
     * ProcessDefinition 流程定义
     *
     * @param deploymentId         部署id
     * @param processDefinitionKey 流程定义key
     * @param pageSiez             每页条数
     * @param pageNum              当前页
     * @return
     */
    @Override
    public Map<String, Object> queryProcessDefinition
    (String deploymentId, String processDefinitionKey, int pageSiez, int pageNum) {
        Map<String, Object> resultData = new HashMap<>();

        List<ProcessDefinition> processDefinitions = repositoryService
                .createProcessDefinitionQuery() // 查询对象
                //查询条件
                .deploymentId(deploymentId) // 部署id
                .processDefinitionKey(processDefinitionKey) // 部署key
                // 排序
                .orderByProcessDefinitionVersion().asc()
                .listPage(pageNum, pageSiez);

        long count = repositoryService
                .createProcessDefinitionQuery() // 查询对象
                //查询条件
                .deploymentId(deploymentId) // 部署id
                .processDefinitionKey(processDefinitionKey) // 部署key
                // 排序
                .orderByProcessDefinitionVersion().asc()
                .count();
        resultData.put("processDefinitions", processDefinitions);
        resultData.put("processDefinitionCount", count);

        for (ProcessDefinition processDefinition : processDefinitions) {
            log.info("流程定义id：" + processDefinition.getId());
            log.info("部署名称：" + processDefinition.getName());
            log.info("部署key：" + processDefinition.getKey());
            log.info("部署版本号version：" + processDefinition.getVersion());
            log.info("部署id：" + processDefinition.getDeploymentId());
            log.info("category：" + processDefinition.getCategory());
            log.info("description：" + processDefinition.getDescription());
            log.info("diagramResourceName：" + processDefinition.getDiagramResourceName());
            log.info("resourceName：" + processDefinition.getResourceName());
            log.info("tenantId：" + processDefinition.getTenantId());
            log.info("engineVersion：" + processDefinition.getEngineVersion());
            log.info("=============================================================");
        }
        return resultData;
    }

    /**
     * 删除流程定义
     *
     * @param deploymentId 部署id
     * @param cascade      是否级联删除
     */
    @Override
    public void deleteProcessDefinition(String deploymentId, boolean cascade) {
        /**
         *  1)因为删除的是流程定义，而流程定义的部署是属于仓库服务的，所以应该先得到RepositoryService
         *
         *  2)如果该流程定义下没有正在运行的流程，则可以用普通删除。如果是有关联的信息，用级联删除。
         * 项目开发中使用级联删除的情况比较多，删除操作一般只开放给超级管理员使用。
         */
        repositoryService.deleteDeployment(deploymentId, cascade);
    }

    /**
     * 删除流程定义
     * 根据流程定义的key删除已经部署的流程，以及流程定义，级联删除
     *
     * @param processDefinitionKey
     */
    @Override
    public void deleteProcessDefinitionByKey(String processDefinitionKey) {
        /**
         *  1)因为删除的是流程定义，而流程定义的部署是属于仓库服务的，所以应该先得到RepositoryService
         *
         *  2)如果该流程定义下没有正在运行的流程，则可以用普通删除。如果是有关联的信息，用级联删除。
         * 项目开发中使用级联删除的情况比较多，删除操作一般只开放给超级管理员使用。
         */
        List<ProcessDefinition> processDefinitions = repositoryService
                .createProcessDefinitionQuery() // 查询对象
                //查询条件
                .processDefinitionKey(processDefinitionKey) // 部署key
                // 排序
                .list();
        for (ProcessDefinition processDefinition : processDefinitions) {
            repositoryService.deleteDeployment(processDefinition.getDeploymentId(), true);
        }
    }

    /**
     * 流程是否已经结束
     *
     * @param processInstanceId 流程实例ID
     * @return
     */
    public boolean isFinished(String processInstanceId) {
        return ProcessEngines.getDefaultProcessEngine().getHistoryService()
                .createHistoricProcessInstanceQuery().finished()
                .processInstanceId(processInstanceId).count() > 0;
    }

    /**
     * 方法一：生成流程图；带进度:<br>
     * 得到带有高亮节点的流程图
     *
     * @param processInstanceId
     *            流程实例id
     * @return
     */
    public void traceProcessImage(String processInstanceId, HttpServletResponse response) throws IOException {
        if (processInstanceId == null) {
            throw new ActivitiIllegalArgumentException(
                    "No process instance id provided");
        }

        ExecutionEntity pi = (ExecutionEntity) ProcessEngines.getDefaultProcessEngine().getRuntimeService()
                .createProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();

        if (pi == null) {
            throw new ActivitiObjectNotFoundException(
                    "Process instance with id" + processInstanceId
                            + " could not be found", ProcessInstance.class);
        }

        ProcessDefinitionEntity pde = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                .getDeployedProcessDefinition(pi.getProcessDefinitionId());

        if (pde != null && pde.isGraphicalNotationDefined()) {
            BpmnModel bpmnModel = repositoryService.getBpmnModel(pde.getId());

            List<String> activeActivityIds = ProcessEngines.getDefaultProcessEngine().getRuntimeService().getActiveActivityIds(processInstanceId);

            InputStream resource = new DefaultProcessDiagramGenerator().generateDiagram(
                    bpmnModel,
                    new ArrayList<String>(),
                    activeActivityIds,
                    pde.getDeploymentId(),
                    "宋体",
                    "宋体",
                    false,
                    pde.getResourceName());


            int len = 0;
            byte[] b = new byte[1024];
//            while ((len = resource.read(b, 0, 1024)) != -1) {
//                response.getOutputStream().write(b, 0, len);
//                log.info(len);
//            }
            BufferedImage img = ImageIO.read(resource);
            ImageIO.write(img, "jpg", new File("D://test.txt"));

        } else {
            throw new ActivitiException("Process instance with id "
                    + processInstanceId + " has no graphic description");
        }
    }
}
